建议 41:使用argparse处理命令行参数
处理命令行参数可以使用argsparse,也推荐更方便更高级的docopt进行处理。
docopt是根据常见的帮助信息定义了一套领域特定语言(DSL),并通过这个DSL Parser参数生成处理命令行参数的代码。
建议 42:使用pandas处理大型CSV文件
pandas作为python三大科学运算库之一的使用。
建议 43:一般情况下使用ElementTree解析xml格式文件
使用Beautifulsoup更好
建议 44:理解模块pickle优劣
序列化,简单来说就是把内存中的数据结构在不丢失其身份和类型信息的情况下转成对象的文本或二进制表示的过程。同类支持序列化的模块有pickle,json,marshal和shelve。
pickle是最通用的序列化模块,我们应该优先使用c语言实现的cPickle,速度比pickle快1000倍,区别是cPickle不能被继承。
pickle主要通过dump和load两种方法序列化与反序列化(存储与读取)
|
|
因为python-message的消息订阅默认是全局性的,所以有可能产生名字冲突。
建议 53:用状态模式美化代码
所谓状态模式,就是当一个对象的内在状态改变时允许改变其行为,但这个对象看起来像是改变了其类。
简单的状态模式有其缺点:
- 查询对象的当前状态很麻烦
- 状态切换时需要对原状态做一些清扫工作,而对新状态做初始化工作,因每个状态需要做的事情不同,全部写在切换状态的代码中必然重复
这时候我们可以使用 Python-state 来解决。
|
|
@statefule装饰器重载了被修饰的类的__getattr__()从而使得 People 的实例能够调用当前状态类的方法,同时被修饰的类的实例是带有状态的,能够使用curr()查询当前状态,也可以使用switch()进行状态切换,默认的状态是通过类定义的 default 属性标识,default = True的类成为默认状态。
状态类 Workday 和 Weekend 继承自 State 类,从其派生的子类可以使用__begin__和__end___状态转换协议,自定义进入和离开当前状态时对宿主的初始化和清理工作。
下面是一个真实业务的例子:
|
|
第 6 章 内部机制
建议 54:理解 built-in objects
Python 中一切皆对象,在新式类中,object 是所有内建类型的基类,用户自定义的类可以继承自 object 也可继承自内建类型。
|
|
新式类支持 property 和描述符特性,作为新式类的祖先,Object 类还定义了一些特殊方法:new()、init()、delattr()、getattribute()、setattr()、hash()、repr()、str()等。
建议 55:init()不是构造方法
|
|
运行结果:
|
|
从结果中我们可以看出,程序输出了__new__()调用所产生的输出,并抛出了异常。于是我们知道,原来__new__()才是真正创建实例,是类的构造方法,而__init__()是在类的对象创建好之后进行变量的初始化。上面程序抛出异常是因为在__new__()中没有显式返回对象,a1此时为None,当去访问实例属性时就抛出了异常。
根据官方文档,我们可以总结以下几点:
- object.new(cls[, args…]):其中 cls 代表类,args 为参数列表,为静态方法
- object.init(self[, args…]):其中 self 代表实例对象,args 为参数列表,为实例方法
- 控制实例创建的时候可使用 new() ,而控制实例初始化的时候使用 init()
- new()需要返回类的对象,当返回类的对象时将会自动调用__init__()进行初始化,没有对象返回,则__init__()不会被调用。init() 方法不需要显示返回,默认为 None,否则会在运行时抛出 TypeError
- 但当子类继承自不可变类型,如 str、int、unicode 或者 tuple 的时候,往往需要覆盖__new__()
- 覆盖 new() 和 init() 的时候这两个方法的参数必须保持一致,如果不一致将导致异常
下面我们来总结需要覆盖__new__()的几种特殊情况:
- 当类继承不可变类型且默认的 new() 方法不能满足需求的时候
- 用来实现工厂模式或者单例模式或者进行元类编程,使用__new__()来控制对象创建
- 作为用来初始化的 init() 方法在多继承的情况下,子类的 init()方法如果不显式调用父类的 init() 方法,则父类的 init() 方法不会被调用;通过super(子类, self).init()显式调用父类的初始化方法;对于多继承的情况,我们可以通过迭代子类的 bases 属性中的内容来逐一调用父类的初始化方法
分别来看例子加深理解:
|
|
建议 56:理解名字查找机制
在 Python 中所谓的变量其实都是名字,这些名字指向一个或多个 Python 对象。这些名字都存在于一个表中(命名空间),我们称之为局部变量,调用locals()可以查看:
|
|
Python 中的作用域分为:
- 局部作用域: 一般来说函数的每次调用都会创建一个新的本地作用域, 拥有新的命名空间
- 全局作用域: 定义在 Python 模块文件中的变量名拥有全局作用域, 即在一个文件的顶层的变量名仅在这个文件内可见
- 嵌套作用域: 多重函数嵌套时才会考虑, 即使使用 global 进行申明也不能达到目的, 其结果最终是在嵌套的函数所在的命名空间中创建了一个新的变量
- 内置作用域: 通过标准库中的__builtin__实现的 当访问一个变量的时候,其查找顺序遵循变量解析机制 LEGB 法则,即依次搜索 4 个作用域:局部作用域、嵌套作用域、全局作用域以及内置作用域,并在第一个找到的地方停止搜寻,如果没有搜到,则会抛出异常。
Python 3 中引入了 nonlocal 关键字:
|
|
建议 57: 为什么需要 self 参数
在类中当定义实例方法的时候需要将第一个参数显式声明为self, 而调用时不需要传入该参数, 我们通过self.x访问实例变量, self.m()访问实例方法:
|
|
运行结果:
|
|
从中可以发现, self 表示实例对象本身, 即 SelfTest 类的对象在内存中的地址. self 是对对象 st 本身的引用, 我们在调用实例方法时也可以直接传入实例对象: SelfTest.display(st). 同时 self 或 cls 并不是 Python 的关键字, 可以替换成其它的名称。
Python 中为什么需要 self 呢:
- 借鉴了其他语言的特征
- Python 语言本身的动态性决定了使用 self 能够带来一定便利
- 在存在同名的局部变量以及实例变量的情况下使用 self 使得实例变量更容易被区分
Python 属于一级对象语言, 我们有好几种方法可以引用类方法:
|
|
Python 的哲学是:显示优于隐式(Explicit is better than implicit)。
建议 58: 理解 MRO 与多继承
古典类与新式类所采取的 MRO (Method Resolution Order, 方法解析顺序) 的实现方式存在差异。
古典类是按照多继承申明的顺序形成继承树结构, 自顶向下采用深度优先的搜索顺序. 而新式类采用的是 C3 MRO 搜索方法, 在新式类通过__mro__得到 MRO 的搜索顺序, C3 MRO 的算法描述如下:
假定,C1C2…CN 表示类 C1 到 CN 的序列,其中序列头部元素(head)=C1,序列尾部(tail)定义 = C2…CN;
C 继承的基类自左向右分别表示为 B1,B2…BN
L[C] 表示 C 的线性继承关系,其中 L[object] = object。
算法具体过程如下:
L[C(B1…BN)] = C + merge(L[B1] … L[BN], B1 … BN)
其中 merge 方法的计算规则如下:在 L[B1]…L[BN],B1…BN 中,取 L[B1] 的 head,如果该元素不在 L[B2]…L[BN],B1…BN 的尾部序列中,则添加该元素到 C 的线性继承序列中,同时将该元素从所有列表中删除(该头元素也叫 good head),否则取 L[B2] 的 head。继续相同的判断,直到整个列表为空或者没有办法找到任何符合要求的头元素(此时,将引发一个异常)。
菱形继承是我们在多继承设计的时候需要尽量避免的一个问题。
建议 59: 理解描述符机制
|
|
.操作符封装了对实例属性和类属性两种不同属性进行查找的细节。
但是如果是访问方法呢:
|
|
根据通过实例访问属性和根据类访问属性的不同,有以下两种情况:
一种是通过实例访问,比如代码 obj.x,如果 x 是一个描述符,那么 getattribute() 会返回 type(obj).dict[‘x’].get(obj, type(obj)) 结果,即:type(obj) 获取 obj 的类型;type(obj).dict[‘x’] 返回的是一个描述符,这里有一个试探和判断的过程;最后调用这个描述符的 get() 方法。
另一个是通过类访问的情况,比如代码 cls.x,则会被 getattribute()转换为 cls.dict[‘x’].get(None, cls)。
描述符协议是一个 Duck Typing 的协议,而每一个函数都有 get 方法,也就是说其他每一个函数都是描述符。所有对属性, 方法进行修饰的方案往往都用到了描述符, 如classmethod, staticmethod, property等, 以下是property的参考实现:
123456789101112131415161718192021class Property(object):"Emulate PyProperty_Type() in Objects/descrobject.c"def __init__(self, fget=None, fset=None, fdel=None, doc=None):self.fget = fgetself.fset = fsetself.fdel = fdelself.__doc__ = docdef __get__(self, obj, objtype=None):if obj is None:return selfif self.fget is None:raise AttributeError, "unreadable attribute"return self.fget(obj)def __set__(self, obj, value):if self.fset is None:raise AttributeError, "can't set attribute"self.fset(obj, value)def __delete__(self, obj):if self.fdel is None:raise AttributeError, "can't delete attribute"self.fdel(obj)
建议 60:区别__getattr__()和__getattribute__()方法
以上两种方法可以对实例属性进行获取和拦截:
- getattr(self, name):适用于属性在实例中以及对应的类的基类以及祖先类中都不存在;
- getattribute(self, name):对于所有属性的访问都会调用该方法。
但访问不存在的实例属性时,会由内部方法__getattribute__()抛出一个 AttributeError 异常,也就是说只要涉及实例属性的访问就会调用该方法,它要么返回实际的值,要么抛出异常。详情请参考。
那么__getattr__()在什么时候调用呢:
- 属性不在实例的__dict__中;
- 属性不在其基类以及祖先类的__dict__中;
- 触发AttributeError异常时(注意,不仅仅是__getattribute__()方法的AttributeError异常,property 中定义的get()方法抛出异常的时候也会调用该方法)。
当这两个方法同时被定义的时候,要么在__getattribute__()中显式调用,要么触发AttributeError异常,否则__getattr__()永远不会被调用。
我们知道 property 也能控制属性的访问,如果一个类中如果定义了 property、getattribute()以及__getattr__()来对属性进行访问控制,会最先搜索__getattribute__()方法,由于 property 对象并不存在于 dict 中,因此并不能返回该方法,此时会搜索 property 中的get()方法;当 property 中的set()方法对属性进行修改并再次访问 property 的get()方法会抛出异常,这时会触发__getattr__()的调用。
getattribute()总会被调用,而__getattr__()只有在__getattribute__()中引发异常的情况下调用。